package tv.dyndns.kishibe.qmaclone.client.creation;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import tv.dyndns.kishibe.qmaclone.client.constant.Constant;
import tv.dyndns.kishibe.qmaclone.client.game.ProblemType;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketProblem;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketWrongAnswer;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.gwt.user.client.ui.IsWidget;

public class WrongAnswerPresenter {

	private static final int MERGE_THRESHOLD = 200;
	private final WrongAnswerView view;

	public WrongAnswerPresenter(WrongAnswerView view) {
		this.view = Preconditions.checkNotNull(view);
	}

	public IsWidget asWidget() {
		return view;
	}

	/**
	 * 文字列を整数に変換する。
	 * 
	 * @param s
	 *            変換元の文字列。
	 * @return　変換後の整数。整数に変換できない場合は0。
	 */
	private int tryParse(String s) {
		try {
			return (int) Math.rint(Double.valueOf(s));
		} catch (NumberFormatException e) {
			return 0;
		}
	}

	private List<PacketWrongAnswer> normalizeClickAnswers(List<PacketWrongAnswer> answers) {
		Map<String, Integer> normalizedAnswerToCount = Maps.newHashMap();
		for (PacketWrongAnswer answer : answers) {
			String[] coord = answer.answer.split(" ");
			if (coord.length != 2) {
				continue;
			}

			int x = Objects.firstNonNull(tryParse(coord[0]), 0);
			int y = Objects.firstNonNull(tryParse(coord[1]), 0);

			String minKey = answer.answer;
			for (Entry<String, Integer> entry : normalizedAnswerToCount.entrySet()) {
				String[] dstCoord = entry.getKey().split(" ");
				int dstX = Objects.firstNonNull(tryParse(dstCoord[0]), 0);
				int dstY = Objects.firstNonNull(tryParse(dstCoord[1]), 0);
				if ((x - dstX) * (x - dstX) + (y - dstY) * (y - dstY) <= MERGE_THRESHOLD) {
					minKey = entry.getKey();
					break;
				}
			}

			int count = Objects.firstNonNull(normalizedAnswerToCount.get(minKey),
					Integer.valueOf(0))
					+ answer.count;
			normalizedAnswerToCount.put(minKey, count);
		}

		List<PacketWrongAnswer> normalized = Lists.newArrayList();
		for (Entry<String, Integer> entry : normalizedAnswerToCount.entrySet()) {
			PacketWrongAnswer answer = new PacketWrongAnswer();
			answer.answer = entry.getKey();
			answer.count = entry.getValue();
			normalized.add(answer);
		}

		return normalized;
	}

	private String extractFileName(String fileName) {
		if (!fileName.startsWith("http") || !fileName.contains("://")) {
			return fileName;
		}

		int offset = fileName.lastIndexOf('/') + 1;
		return fileName.substring(offset);
	}

	/**
	 * 解答文字列の正規化を行う。実際に行うのはURLからのファイル名の抽出と、解答のソート
	 * 
	 * @param answer
	 * @return
	 */
	private String normalizeAnswerString(String answer) {
		List<String> pairs = Lists.newArrayList();

		// 複数回答に対応する
		for (String pair : Splitter.on(Constant.DELIMITER_GENERAL).split(answer)) {
			List<String> elements = Lists.newArrayList();

			// 線結びに対応する
			for (String element : Splitter.on(Constant.DELIMITER_KUMIAWASE_PAIR).split(pair)) {
				// URLはファイル名のみ表示する
				elements.add(extractFileName(element));
			}

			pairs.add(Joiner.on(Constant.DELIMITER_KUMIAWASE_PAIR).join(elements));
		}

		return Joiner.on(Constant.DELIMITER_GENERAL).join(pairs);
	}

	/**
	 * 複数回答をソートする
	 * 
	 * @param answer
	 * @return
	 */
	private String sortMultipleStrings(String answer) {
		List<String> pairs = Lists.newArrayList(Splitter.on(Constant.DELIMITER_GENERAL).split(
				answer));
		Collections.sort(pairs);
		return Joiner.on(Constant.DELIMITER_GENERAL).join(pairs);
	}

	/**
	 * 正規化後の解答の回答数を再度集計する
	 * 
	 * @param answers
	 * @return 再集計結果。結果は解答文字列でソートされている
	 */
	private List<PacketWrongAnswer> recountAnswers(List<PacketWrongAnswer> answers) {
		Map<String, Integer> normalizedAnswerToCount = Maps.newHashMap();
		for (PacketWrongAnswer answer : answers) {
			int count = Objects.firstNonNull(normalizedAnswerToCount.get(answer.answer),
					Integer.valueOf(0))
					+ answer.count;
			normalizedAnswerToCount.put(answer.answer, count);
		}

		List<PacketWrongAnswer> normalized = Lists.newArrayList();
		for (Entry<String, Integer> entry : normalizedAnswerToCount.entrySet()) {
			PacketWrongAnswer answer = new PacketWrongAnswer();
			answer.answer = entry.getKey();
			answer.count = entry.getValue();
			normalized.add(answer);
		}

		Collections.sort(normalized, new Comparator<PacketWrongAnswer>() {
			@Override
			public int compare(PacketWrongAnswer o1, PacketWrongAnswer o2) {
				return ComparisonChain.start()
						.compare(o1.count, o2.count, Ordering.natural().reverse())
						.compare(o1.answer, o2.answer).result();
			}
		});

		return normalized;
	}

	@VisibleForTesting
	List<PacketWrongAnswer> normalize(List<PacketWrongAnswer> wrongAnswers, PacketProblem problem) {
		// 画像クリッククイズは近い座標をマージする
		if (problem.type == ProblemType.Click) {
			wrongAnswers = normalizeClickAnswers(wrongAnswers);
		}

		// 各解答文字列の正規化を行う
		for (PacketWrongAnswer wrongAnswer : wrongAnswers) {
			wrongAnswer.answer = normalizeAnswerString(wrongAnswer.answer);
		}

		// 各解答文字列をソートする。順番当てはソートしない。
		// BugTrack-QMAClone/633 - QMAClone wiki
		// http://kishibe.dyndns.tv/qmaclone/wiki/wiki.cgi?page=BugTrack%2DQMAClone%2F633
		if (problem.type != ProblemType.Junban) {
			for (PacketWrongAnswer wrongAnswer : wrongAnswers) {
				wrongAnswer.answer = sortMultipleStrings(wrongAnswer.answer);
			}
		}

		return recountAnswers(wrongAnswers);
	}

	public void setWrongAnswers(List<PacketWrongAnswer> wrongAnswers, PacketProblem problem) {
		view.setAnswer(normalize(wrongAnswers, problem));
	}

}
